package main;

import java.io.*;
import java.net.*;

//Nikolas Wolfe
//4/3/09
//CNT 4007
//Reliable Data Transfer

public class DelayNetwork
{
	// constants
	private static final String MAKE_PKT = "make_pkt(";
	
	// network settings
	private final int port;
	private final DatagramSocket senderSocket;
	private final DatagramSocket receiverSocket;
	private final Thread senderListener;
	private final Thread receiverListener;
	
	// receiver settings
	private int receiverPort;
	private String receiverHostname;
	private InetAddress receiverIPAddress;
	
	// sender settings
	private volatile int senderPort;
	private volatile InetAddress senderIPAddress;
	
	// constructor
	public DelayNetwork(int portNumber) throws SocketException, UnknownHostException
	{
		this.port = portNumber;
		this.senderPort = portNumber;
		this.receiverPort = portNumber;
		
		this.senderSocket = new DatagramSocket(port);
		this.receiverSocket = new DatagramSocket(port+1);
		
		this.senderListener = new Thread(new SenderThread());
		this.receiverListener = new Thread(new ReceiverThread());
	}
	
	// control methods
	public void start()
	{
		senderListener.start();
		receiverListener.start();
	}
	
	public void stop()
	{
		senderListener.interrupt();
		receiverListener.interrupt();
	}
	
	// sender operation class -- IN from sender ==> OUT to receiver
	private class SenderThread extends NetworkThread
	{
		public void run()
		{
			while( !Thread.interrupted() )
			{
				try
				{
					// wait for data from sender, relay back
					DatagramPacket pkt = makeReceivePacket();
					senderSocket.receive(pkt);
					String response = convertPacketToString(pkt);
					
					// set the sender values for the receiver thread
					senderIPAddress = pkt.getAddress();
					senderPort = pkt.getPort();
					
					System.out.println("Incoming from: " + senderIPAddress + 
							" Port #" + senderPort);
					System.out.println("Relay Message: " + response);
					
					int timeout; 
					if( isCorrectPackageString(response) )
					{
						timeout = getTimeout(response);
						if (timeout <= 10000) // else we're dropping packet
						{
							receiverIPAddress = getForwardingHostname(response);
							receiverPort = getForwardingPort(response);

							synchronized (this)
							{
								System.out.println("Waiting for " + timeout + "ms...");
								this.wait(timeout);
							}
							setSendData(response);
							receiverSocket.send(makeSendPacket());
						}
					}
				}
				catch( InterruptedException e )
				{
					Thread.currentThread().interrupt();
				} 
				catch (IOException e)
				{
					System.out.println("IO Exception! Data not properly received!");
					Thread.currentThread().interrupt();
				}
			}
		}
		private InetAddress getForwardingHostname(String s) throws UnknownHostException
		{
			int x = s.indexOf("[");
			int y = s.indexOf("]");
			String q = s.substring(x+1, y);
			String[] arr = q.split(",");
			receiverHostname = arr[0].trim();
			InetAddress returnAddr = InetAddress.getByName(receiverHostname);
			return returnAddr;
		}
		private int getForwardingPort(String s)
		{
			int x = s.indexOf("[");
			int y = s.indexOf("]");
			String q = s.substring(x+1, y);
			String[] arr = q.split(",");
			receiverPort = Integer.parseInt(arr[1].trim());
			return receiverPort;
		}
		private int getTimeout(String s)
		{
			int x = s.indexOf("(");
			int y = s.indexOf(")");
			String q = s.substring(x+1, y);
			String[] arr = q.split(",");
			return Integer.parseInt(arr[3].trim());
		}
		private boolean isCorrectPackageString(String s)
		{
			return s.startsWith(MAKE_PKT);
		}
		@Override
		protected InetAddress getOutgoingIPAddress()
		{
			if( receiverIPAddress == null )
				throw new IllegalStateException("Sender has not initiated communication!");
			return receiverIPAddress;
		}
		@Override
		protected int getOutgoingPort()
		{
			if( receiverIPAddress == null )
				throw new IllegalStateException("Sender has not initiated communication!");
			return receiverPort;
		}
	}
	
	// receiver operation class -- IN from receiver ==> OUT to sender
	private class ReceiverThread extends NetworkThread
	{
		public void run()
		{
			while( !Thread.interrupted() )
			{
				try
				{
					// wait for data from sender, relay back
					DatagramPacket pkt = makeReceivePacket();
					receiverSocket.receive(pkt);
					System.out.println("Incoming from: " + senderIPAddress + 
							"Port #" + senderPort);
					
					String response = convertPacketToString(pkt);
					
					// assumed processing...
					setSendData(response);
					
					senderSocket.send(makeSendPacket());
				}
//				catch( InterruptedException e )
//				{
//					Thread.currentThread().interrupt();
//				} 
				catch (IOException e)
				{
					System.out.println("IO Exception! Data not properly received!");
					Thread.currentThread().interrupt();
				}
			}
		}
		@Override
		protected InetAddress getOutgoingIPAddress()
		{
			if( senderIPAddress == null )
				throw new IllegalStateException("Sender has not initiated communication!");
			return senderIPAddress;
		}
		@Override
		protected int getOutgoingPort()
		{
			if( senderIPAddress == null )
				throw new IllegalStateException("Sender has not initiated communication!");
			return senderPort;
		}
	}
}
